/***************************************************************************
 *   HTTP TCP/IP client socket SDL_RWops stream implimentation             *
 *                                                                         *
 *   Copyright (C) 2005 by Tyler Montbriand                                *
 *   tsm@accesscomm.ca                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

//for strcasestr
#define _GNU_SOURCE

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef __APPLE__
#include "SDL_net.h"
#else
#include <SDL/SDL_net.h>
#endif
#include <SDL/SDL_rwops.h>
#include "SDL_rwlib_internal.h"

#define PKGNAME HTTP

void encode_base64(unsigned char *bufin, int len, char *bufout);
int base64_size(int bytes);

static char *strdupcat(char *dest, const char *src)
{
  int len=strlen(dest)+strlen(src)+1;
  char *out=malloc(len+1);
  strcpy(out,dest);
  strcat(out,src);
  return(out);
}

static char *sock_gets(char *str, int max, TCPsocket sock)
{
  char *pos=str;
  if((max<=1)||(str==NULL))
    return(NULL);

  while(max>1)
  {
    char c;
    if(SDLNet_TCP_Recv(sock,&c,1)<=0)
    {
      if(pos==str) return(NULL);

      (*pos)='\0';
      return(str);
    }
    (*pos)=c;
    max--;
    pos++;

    if(c=='\n') break;
  }

  (*pos)='\0';
  return(str);
}

static int strissomething(const char *str, int (*fn)(int))
{
  if((str==NULL)||(fn==NULL)) return(0);
  if(str[0]=='\0') return(0);

  while((*str)!='\0')
  {
    if(!fn(*str))
      return(0);
    str++;
  }

  return(1);
}

#define STRISDIGIT(str) strissomething(str,isdigit)
#define STRISSPACE(str) strissomething(str,isspace)

RWFUNC_SEEK_STUB(PKGNAME);
RWFUNC_WRITE_STUB(PKGNAME);
RWFUNC_READ(PKGNAME);
RWFUNC_CLOSE(PKGNAME);

static void free_strlist(char **strlist)
{
  int n;
  for(n=0; strlist[n]!=NULL; n++)
    free(strlist[n]);
  free(strlist);
}

static char **HTTP_Transact(TCPsocket sock, const char *str,...)
{
  int arraylen=0;
  char **array=NULL;
  char buf[1024];

  if(str!=NULL)
  {
    va_list ap;
    int bytes;
    va_start(ap,str);
      bytes=vsprintf(buf,str,ap);
      if(bytes>0)
        SDLNet_TCP_Send(sock,buf,bytes);
    va_end(ap);
  }

  /*fprintf(stderr,"%s",buf);*/

  SDLNet_TCP_Send(sock,"\r\n",2);

  while(sock_gets(buf,512,sock)!=NULL)
  {
    if(STRISSPACE(buf))
      break;
    else if(isspace(buf[0]))
    {
      char *nr;
      if(arraylen<1)
        return(NULL);

      nr=strdupcat(array[arraylen-1],buf);
      free(array[arraylen-1]);
      array[arraylen-1]=nr;
    }
    else
    {
      array=realloc(array,(arraylen+2)*sizeof(char *));
      array[arraylen++]=strdup(buf);
      array[arraylen]=NULL;
    }
  }

  return(array);
}

SDL_RWops *SDL_RWFromHTTP(const char *auth,
                          const char *host,
                          const char *path,
                          Uint16      port)
{
  int n,httpcode;
  char **response;
  SDL_RWops *rw=SDL_AllocRW();
  IPaddress ip;
  TCPsocket sock;

  if(rw==NULL)
    return(NULL);

//  if(headers==NULL) headers="";

  if(SDLNet_ResolveHost(&ip,host,port)<0)
  {
    SDL_FreeRW(rw);
    return(NULL);
  }

  sock=SDLNet_TCP_Open(&ip);
  if(sock==NULL)
  {
    SDL_FreeRW(rw);
    return(NULL);  
  }

  RW_INITFUNC(rw,PKGNAME);
  RW_DATAPTR1(rw)=(void *)sock;
  RW_DATAINT(rw)=-1;

  if(auth!=NULL)
  {
    char authbuf[512];
    encode_base64(auth,-1,authbuf);

    response=HTTP_Transact(sock,"GET %s HTTP/1.1\r\n"
                                "Host: %s:%d\r\n"
                                "User-agent: SDL_rwhttp/1.0\r\n"
                                "Connection: close\r\n"
                                "Authorization: Basic %s\r\n",
                                path,host,port,authbuf);
  }
  else
  {
    response=HTTP_Transact(sock,"GET %s HTTP/1.1\r\n"
                                "Host: %s:%d\r\n"
                                "User-agent: SDL_rwhttp/1.0\r\n"
                                "Connection: close\r\n",
                                path,host,port);
  }

HTTP_TRANSACT_LOOP:
  if(response==NULL)
  {
    SDL_SetError("%s: Error reading reply from HTTP",__FUNCTION__);
    SDL_RWclose(rw);
    return(NULL);
  }

  if(sscanf(response[0],"HTTP/%*d.%*d %d",&httpcode)!=1)
  {
    SDL_SetError("%s: Couldn't extract HTTP response code from \"%s\"",
      __FUNCTION__,response[0]);

    free_strlist(response);
    SDL_RWclose(rw);
    return(NULL);
  }

  for(n=1; response[n]!=NULL; n++)
  {
    char header[128],value[512];
    if(sscanf(response[n],"%[^:]%*[:] %[^\r\n]",header,value)!=2)
    {
      fprintf(stderr,"Couldn't process %s",response[n]);
      continue;
    }

    if((strcasecmp("Transfer-Encoding",header)==0)&&
      (strcasestr(value,"chunked")!=NULL))
      {
        /*fprintf(stderr,"> Using chunked encoding!  Wah!\n");*/
        RW_DATAINT(rw)=0;
      }
  }

  free_strlist(response);
  switch(httpcode)
  {
  case 100:
    /*fprintf(stderr,"> Reply hazy, try again later\n");*/
    response=HTTP_Transact(sock,NULL);
    goto HTTP_TRANSACT_LOOP;

  case 200:
    /*fprintf(stderr,"> In the green.\n");*/
    break;

  case 404:
    SDL_SetError("%s: 404 File not found",__FUNCTION__);
    SDL_RWclose(rw);
    return(NULL);

  default:
    SDL_SetError("%s: Unhandled HTTP response %d",__FUNCTION__,httpcode);
    SDL_RWclose(rw);
    return(NULL);  
  }

  return(rw);
}

RWFUNC_READ(PKGNAME)
  /*(SDL_RWops *ctx, void *ptr, int size, int maxnum)*/
{
  int bread;

  if(RW_DATAPTR1(ctx)==NULL)
  {
    SDL_SetError("%s:  Invalid RWops context",__FUNCTION__);
    return(-1);
  }

  if(RW_DATAINT(ctx)<0)
    return(SDLNet_TCP_Recv((TCPsocket)RW_DATAPTR1(ctx),ptr,size*maxnum));
  else if(RW_DATAINT(ctx)==0)
  {
    char buf[512];

    if(sock_gets(buf,512,(TCPsocket)RW_DATAPTR1(ctx))==NULL)
      return(-1);
    if(sscanf(buf,"%x",&RW_DATAINT(ctx))!=1)
    {
      SDL_SetError("%s: Couldn't extract chunk size from chunked reply",
        __FUNCTION__);
      return(-1);
    }

    if(RW_DATAINT(ctx)==0)
    {
      SDLNet_TCP_Close((TCPsocket)RW_DATAPTR1(ctx));
      RW_DATAPTR1(ctx)=NULL;
      RW_DATAINT(ctx)=-1;
      SDL_SetError("%s:  Transfer complete, connection closed",__FUNCTION__);
      return(-1);
    }
  }

  if(RW_DATAINT(ctx) >= (size*maxnum))
  {
    bread=SDLNet_TCP_Recv((TCPsocket)RW_DATAPTR1(ctx),
                           ptr,size*maxnum);
    if(bread<=0) return(-1);
    RW_DATAINT(ctx)-=bread;
  }
  else
  {
    bread=SDLNet_TCP_Recv((TCPsocket)RW_DATAPTR1(ctx),
                           ptr,RW_DATAINT(ctx));
    if(bread<=0) return(-1);
    RW_DATAINT(ctx)-=bread;
  }


  if(RW_DATAINT(ctx)==0) // Read chunk, need to get CRLF next
  {
    char buf[3];
    SDLNet_TCP_Recv((TCPsocket)RW_DATAPTR1(ctx),buf,2);
//    sock_gets(buf,3,(TCPsocket)RW_DATAPTR1(ctx));
  }

  return(bread);
}

RWFUNC_CLOSE(PKGNAME)
  /*(SDL_RWops *ctx)*/
{
  if(ctx==NULL)
  {
    SDL_SetError("%s:  Invalid RWops",__FUNCTION__);
    return(-1);
  }
  else if(RW_TYPE(ctx)!=RW_TYPENUM(SOCK))
  {
    SDL_SetError("%s:  RWops is the wrong type",__FUNCTION__);
    return(-1);
  }

  SDLNet_TCP_Close((TCPsocket)RW_DATAPTR1(ctx));
  SDL_FreeRW(ctx);
  return(0);
}

extern const char base64_tab[64];
extern const char base64_inverse[128];

union b64grp
{
  Uint32 bytes;
  struct
  {
    unsigned int a:6;
    unsigned int b:6;
    unsigned int c:6;
    unsigned int d:6;
  } bits;
};

#define LINE_MAX 76


int base64_size(int bytes)
{
  int extra=bytes%3;
  bytes+=(extra^3);
  bytes=(bytes*4)/3;
  bytes+=(bytes/72)*2;
  return(bytes+1);
}

void encode_base64(unsigned char *bufin, int len, char *bufout)
{
  union b64grp grp;
  int pos=0;

  if(len<0) len=strlen(bufin);

  while((len-pos)>3)
  {
    grp.bytes=(bufin[2])|(bufin[1]<<8)|(bufin[0]<<16);

    bufout+=sprintf(bufout,"%c%c%c%c",
        base64_tab[grp.bits.d],
        base64_tab[grp.bits.c],
        base64_tab[grp.bits.b],
        base64_tab[grp.bits.a]);
    
    bufin+=3;
    pos+=3;

    if((pos%72)==0)
      bufout+=sprintf(bufout,"\r\n");
  }

  switch(len-pos)
  {
  case 2:
    grp.bytes=(bufin[1]<<8)|(bufin[0]<<16);
    bufout+=sprintf(bufout,"%c%c%c=",
      base64_tab[grp.bits.d],
      base64_tab[grp.bits.c],
      base64_tab[grp.bits.b]);
    break;
  case 1:
    grp.bytes=(bufin[0]<<16);    
    bufout+=sprintf(bufout,"%c%c==",
      base64_tab[grp.bits.d],
      base64_tab[grp.bits.c]);
    break;

  case 0:
    break;
  }
}

const char base64_tab[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                          "abcdefghijklmnopqrstuvwxyz"
                          "0123456789+/";

const char base64_inverse[128]={
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
        , -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
        , -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
        , -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
        , -1, -1, -1, 62, -1, -1, -1, 63, 52, 53
        , 54, 55, 56, 57, 58, 59, 60, 61, -1, -1
        , -1, -1, -1, -1, -1,  0,  1,  2,  3,  4
        ,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14
        , 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
        , 25, -1, -1, -1, -1, -1, -1, 26, 27, 28
        , 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
        , 39, 40, 41, 42, 43, 44, 45, 46, 47, 48
        , 49, 50, 51, -1, -1, -1, -1, -1 };

/* This was the code that generated the inverse table */
/*
  for(n=0; n<128; n++) base64_inverse[n]=-1;
  for(n=0; base64_tab[n]!='\0'; n++)
    base64_inverse[base64_tab[n]]=n;

  printf("char base64_inverse[128]={\n\t%d",base64_inverse[0]);
  for(n=1; n<128; n++)
  {
    if((n%10)==0) printf("\n\t");
    printf(", %d",base64_inverse[n]);
  }
  printf("};\n");*/

#ifdef HTTP_TEST
int main(int argc, char *argv[])
{
  int bread,port=80;
  char buf[256];
  SDL_RWops *rw;

  rw=SDL_RWFromHTTP("burningsmell.org","/index.html",80);
  if(rw==NULL)
  {
    fprintf(stderr,"Couldn't open HTTP RWops\n");
    return(2);
  }

  while((bread=SDL_RWread(rw,&buf,1,256))>0)
    fwrite(buf,bread,1,stdout);

  SDL_RWclose(rw);

  return(0);
}
#endif
